from BasicDefinition import *
import random
import matplotlib.pyplot as plt


class Individual:
    def __init__(self, arg1, Cof_wwt, Cof_wd, Cof_wtt, cx, cy, network: Network, block_idx):
        self.posx = cx
        self.PT = 0
        self.TD = 0
        self.posy = cy
        self.Satellite_idx = network.CoordinationMapping(cx, cy)
        self.DeterministicSatellite = network.SatelliteGroup[cx][cy]
        self.block_idx = block_idx
        self.TaskIndices = self.DeterministicSatellite.TaskBlock[self.block_idx]
        self.Net = network
        self.ProximitySatellites = network.GetAdjSatellites(cx, cy).copy()
        self.chromosome = []
        if isinstance(arg1, int):
            self.length = arg1
            random.shuffle(self.ProximitySatellites)
            self.chromosome.append(self.ProximitySatellites[0])
            for ele in self.ProximitySatellites[1:]:
                now = self.chromosome[-1]
                if self.Net.BandWithMatrix[now][ele] != 0:
                    self.chromosome.append(ele)
            if arg1 <= len(self.chromosome):
                self.chromosome = self.chromosome[:arg1]
            else:
                while len(self.chromosome) < arg1:
                    pos = random.randint(0, len(self.chromosome) - 1)
                    self.chromosome.insert(pos, self.chromosome[pos])
            # self.chromosome = self.ProximitySatellites[:arg1]
        elif isinstance(arg1, list):
            self.chromosome = arg1
            self.length = len(arg1)
        self.AbandonSet = set()
        self.TT = [0 for _ in range(self.length)]
        self.deficit = self.GetDeficit(Cof_wwt, Cof_wd, Cof_wtt)
        self.Implemented = False

    def IsValid(self):
        S = set()
        S.add(self.chromosome[0])
        for i in range(1, self.length):
            if self.chromosome[i] != self.chromosome[i - 1]:
                if self.chromosome[i] in S:
                    return False
                else:
                    S.add(self.chromosome[i])
        return True

    def GetDeficit(self, Cof_wwt, Cof_wd, Cof_wtt):
        self.AbandonSet.clear()
        ProcessedTime, TransmissionTime, TotalDrop = 0, 0, 0
        WeightTime = 0
        # if not self.IsValid():
            # return float("inf")
        for i, idx in enumerate(self.chromosome):
            This_Satellite = self.Net.Get(idx)
            task_slices = [(self.DeterministicSatellite.TaskList[task_idx].Slice[i] if i < len(
                self.DeterministicSatellite.TaskList[task_idx].Slice) else 0, task_idx) for task_idx in
                           self.TaskIndices]
            IncreasedProcessedTime, IncreasedDrop = This_Satellite.Load(task_slices, self.AbandonSet)
            WeightTime += IncreasedProcessedTime / This_Satellite.ResidualResource()
            ProcessedTime += IncreasedProcessedTime
            TotalDrop += IncreasedDrop
            if i < self.length - 1:
                TransmissionWorkload = sum(
                    [workload for (workload, task_idx) in task_slices if task_idx not in self.AbandonSet])
                self.TT[i] = TransmissionWorkload * self.Net.BandWithMatrix[idx][self.chromosome[i + 1]]
                TransmissionTime += self.TT[i]

        self.TD = TotalDrop
        self.PT = ProcessedTime
        self.OffloadAll()
        return Cof_wwt * WeightTime + Cof_wtt * TransmissionTime + Cof_wd * TotalDrop

    def ImplementSingle(self, chromosome_idx):
        This_Satellite = self.Net.Get(self.chromosome[chromosome_idx])
        # print("DD",self.AbandonSet,end=" ")
        task_indices = self.DeterministicSatellite.TaskBlock[self.block_idx]
        task_slices = [
            (self.DeterministicSatellite.TaskList[task_idx].Slice[chromosome_idx] if chromosome_idx < len(
                self.DeterministicSatellite.TaskList[task_idx].Slice) else 0, task_idx) for task_idx in
            task_indices]
        ProcessedTime, Drop = This_Satellite.Load(task_slices, self.AbandonSet)
        # print('CC', chromosome_idx, task_indices, task_slices, self.AbandonSet, Drop)
        return ProcessedTime, Drop

    def ImplementAll(self):
        self.AbandonSet.clear()
        for i in range(self.length):
            self.ImplementSingle(i)

    def OffloadSingle(self, chromosome_idx):
        self.Net.Get(self.chromosome[chromosome_idx]).Offload(self.TaskIndices)

    def OffloadAll(self):
        for idx in range(self.length):
            self.OffloadSingle(idx)


class Group:
    def __init__(self, InitialNumber, ChromosomeLength, network: Network, Cof_wwt, Cof_wd, Cof_wtt, cx, cy, block_idx):
        self.ChromosomeLength = ChromosomeLength
        self.ProximitySatellites = network.GetAdjSatellites(cx, cy).copy()
        self.DeterministicSatellite = network.SatelliteGroup[cx][cy]
        self.Block = [self.DeterministicSatellite.TaskList[idx] for idx in
                      self.DeterministicSatellite.TaskBlock[block_idx]]
        self.posx, self.posy = cx, cy
        self.Cof_wwt = Cof_wwt
        self.Cof_wtt = Cof_wtt
        self.Cof_wd = Cof_wd
        self.block_idx = block_idx
        self.Net = network
        self.IndividualList = [Individual(self.ChromosomeLength, Cof_wwt, Cof_wd, Cof_wtt, cx, cy, network, block_idx)
                               for _ in
                               range(InitialNumber)]
        # for idx in self.ProximitySatellites:
        #     self.IndividualList.append(
        #         Individual([idx for _ in range(ChromosomeLength)], Cof_wt, Cof_wd, cx, cy, network, block_idx))

    def Mate(self, ind1_idx, ind2_idx):
        offsprings = []
        Ind1, Ind2 = self.IndividualList[ind1_idx].chromosome, self.IndividualList[
            ind2_idx].chromosome
        for i, dig1 in enumerate(Ind1):
            for j, dig2 in enumerate(Ind2):
                if dig1 == dig2:
                    if i < j:
                        offspring_chromosome = (Ind2[:j] +
                                                Ind1[i:i + self.ChromosomeLength - j])
                        if offspring_chromosome != Ind1 and offspring_chromosome != Ind2:
                            offspring = Individual(offspring_chromosome, self.Cof_wwt, self.Cof_wd, self.Cof_wtt,
                                                   self.posx, self.posy, self.Net, self.block_idx)
                            if offspring.IsValid():
                                offsprings.append(offspring)
                    if i == j:
                        offspring_chromosome1 = (Ind2[:j] +
                                                 Ind1[i:i + self.ChromosomeLength - j])
                        if offspring_chromosome1 != Ind1 and offspring_chromosome1 != Ind2:
                            offspring1 = Individual(offspring_chromosome1, self.Cof_wwt, self.Cof_wd, self.Cof_wtt,
                                                    self.posx, self.posy, self.Net, self.block_idx)
                            if offspring1.IsValid():
                                offsprings.append(offspring1)
                        offspring_chromosome2 = (Ind1[:i] +
                                                 Ind2[j:j + self.ChromosomeLength - i])
                        if offspring_chromosome2 != Ind1 and offspring_chromosome2 != Ind2:
                            offspring2 = Individual(offspring_chromosome2, self.Cof_wwt, self.Cof_wd, self.Cof_wtt,
                                                    self.posx, self.posy, self.Net, self.block_idx)
                            if offspring2.IsValid():
                                offsprings.append(offspring2)

                    if i > j:
                        offspring_chromosome = (Ind1[:i] +
                                                Ind2[j:j + self.ChromosomeLength - i])
                        if offspring_chromosome != Ind1 and offspring_chromosome != Ind2:
                            offspring = Individual(offspring_chromosome, self.Cof_wwt, self.Cof_wd, self.Cof_wtt,
                                                   self.posx, self.posy, self.Net, self.block_idx)
                            if offspring.IsValid():
                                offsprings.append(offspring)
        return offsprings

    def Reproduce(self):
        N = len(self.IndividualList)
        for i in range(N - 1):
            for j in range(i + 1, N):
                self.IndividualList.extend(self.Mate(i, j))

    def Eliminate(self, MaxAllowedGroupSize):
        self.IndividualList.sort(key=lambda Ind: Ind.deficit)
        while len(self.IndividualList) > MaxAllowedGroupSize:
            self.IndividualList.pop()

    def Scheme(self):
        self.IndividualList[0].ImplementAll()

    def Descheme(self):
        self.IndividualList[0].OffloadAll()

    def Iterate(self, epoch, epsilon, MaxGroupSize, InsertingNumber):
        last_prior_one = float("inf")
        times = 0
        for i in range(epoch):
            self.Reproduce()
            for _ in range(InsertingNumber):
                self.IndividualList.append(
                    Individual(self.ChromosomeLength, self.Cof_wwt, self.Cof_wtt, self.Cof_wd, self.posx, self.posy,
                               self.Net, self.block_idx))
            self.Eliminate(MaxGroupSize)
            prior_one = self.IndividualList[0].deficit
            times = i
            if abs(prior_one - last_prior_one) < epsilon:
                break
            last_prior_one = prior_one
        # print("Iterating Times:", times + 1)
        Ind = self.IndividualList[0]
        ResourceDistribution = [[1 for _ in range(self.Net.SatelliteNumber)] for __ in range(self.Net.OrbitNumber)]
        for i in range(self.Net.OrbitNumber):
            for j in range(self.Net.SatelliteNumber):
                ResourceDistribution[i][j] = sum(self.Net.SatelliteGroup[i][j].CPU_WorkLoad_List) / (
                        self.Net.SatelliteGroup[i][j].CPU_Number * self.Net.SatelliteGroup[i][j].MaxCPUWorkLoad)
        return Ind.PT + sum(Ind.TT), Ind.TD, ResourceDistribution


def GA_Decision(network: Network, cx, cy, block_idx, InitialNumber, ChromosomeLength, Cof_wwt, Cof_wd, Cof_wtt, epoch,
                epsilon, MaxGroupSize, InsertingNumber):
    group = Group(InitialNumber, ChromosomeLength, network, Cof_wwt, Cof_wd, Cof_wtt, cx, cy, block_idx)
    group.Iterate(epoch, epsilon, MaxGroupSize, InsertingNumber)
    # print(group.IndividualList[0].chromosome, block_idx)
    return group.IndividualList[0]

# network = Network(5, 5, 1, 3, 9, 10, 200, 50, 4, 2000, 1000, 1)
#
# drop_curve = []
# time_curve = []
# n = len(network.SatelliteGroup[5][5].TaskBlock)
# cnt = network.SatelliteGroup[5][5].TaskNumber
# for i in range(n):
#     zgroup = Group(10, 3, network, 20, 1000000, 1, 5, 5, i)
#     for ele in zgroup.IndividualList:
#         print(ele.chromosome, ele.deficit)
#     TT, TD, RD = zgroup.Iterate(40, 0.1, 10)
#     print(zgroup.IndividualList[0].chromosome, zgroup.IndividualList[0].TaskIndices, zgroup.IndividualList[0].deficit)
#     if zgroup.IndividualList[0].TD > 0:
#         print(zgroup.IndividualList[0].AbandonSet)
#     zgroup.Scheme()
#     # zgroup.Descheme()
#     drop_curve.append(TD)
#     time_curve.append(TT)
#     for orbit in RD:
#         for s in orbit:
#             print('{0: <5}'.format(round(s, 3)), end=" ")
#         print(" ")
#
# plt.plot([i for i in range(n)], drop_curve)
# plt.show()
#
# plt.plot([i for i in range(n)], time_curve)
# plt.show()
#
# print(drop_curve)
# print(cnt)
# # for i in range(100):
# #     x = Individual([1, 1, 2, 2, 3], 1, 1, 4, 4, network, 0)
# #     if x.IsValid():
# #         print(x.chromosome)
